// Package watcher 本包仅适用于配合服务注册发现服务(ServiceManager)使用;
// 如需更多功能可在此包增加扩展;
package watcher

import (
	"errors"
	"github.com/fanjindong/go-cache"
	"go.uber.org/zap"
	"gobase/http"
	"gobase/logger"
	"gobase/utils"
	"math/rand"
	"strings"
	"time"
)

// Config 服务发现实例配置
type Config struct {
	// 服务注册发现服务器IP
	ServiceManagerIP string
	// 服务注册发现服务器端口
	ServiceManagerPort string
	// 服务注册发现服务器k8s域名,如果为空则自动根据ip端口拼接地址
	ServiceManagerDnsUrl string
	// 缓存过期时间,默认60秒
	ExpireTime time.Duration
}

type ServWatcher struct {
	clt  *http.Client
	conf *Config
	mc   cache.ICache
	self *ServiceInfoBase
}

type ServiceQueue []ServiceInfoAndStatus

// NewServWatcher 初始化一个服务发现实例
func NewServWatcher(conf *Config) (*ServWatcher, error) {
	if conf.ServiceManagerDnsUrl == "" {
		conf.ServiceManagerDnsUrl = "http://" + conf.ServiceManagerIP
	}
	if conf.ExpireTime == 0 {
		conf.ExpireTime = 60 * time.Second
	}
	clt := http.DefaultClient()
	mc := cache.NewMemCache()
	sw := &ServWatcher{
		clt:  clt,
		conf: conf,
		mc:   mc,
	}
	return sw, nil
}

func (sw *ServWatcher) RegisterService(serv ServiceInfoBase) error {
	resp := sw.clt.NewRequest().SetData(&serv).Post(utils.StringBuilder(sw.conf.ServiceManagerDnsUrl, ":", sw.conf.ServiceManagerPort, "/v1/register"))
	if err := resp.Err(); err != nil {
		return err
	}
	logger.Info("RegisterServiceResponse", zap.ByteString("response", resp.GetBody()))
	if resp.Status != 200 {
		return errors.New(utils.StringBuilder("register service error: ", string(resp.GetBody())))
	}
	sw.self = &serv
	return nil
}

func (sw *ServWatcher) UnregisterService() error {
	if sw.self == nil {
		return errors.New("service not register or failed")
	}
	resp := sw.clt.NewRequest().SetData(&UnregisterRequest{ServiceID: sw.self.ServiceID, ServiceName: sw.self.ServiceName}).Post(utils.StringBuilder(sw.conf.ServiceManagerDnsUrl, ":", sw.conf.ServiceManagerPort, "/v1/unregister"))
	if err := resp.Err(); err != nil {
		return err
	}
	sw.self = nil
	return nil
}

// FoundServicesWithName 根据服务名查询服务列表, id为空则为该服务名全部服务列表
func (sw *ServWatcher) FoundServicesWithName(name, id string) (ServiceQueue, error) {
	key := utils.StringBuilder("service_", name, "_", id)
	if id == "" {
		key = utils.StringBuilder("service_", name, "_all")
	}
	list := make([]ServiceInfoAndStatus, 0)
	// 从缓存获取服务列表
	val, ok := sw.mc.Get(key)
	if ok {
		list, ok = val.([]ServiceInfoAndStatus)
		if !ok {
			return nil, errors.New("mem-cache Value Type error")
		}
		return list, nil
	}
	// 缓存不存在或已过期向服务注册发现服务器获取服务列表
	req := &WatcherRequest{ServiceName: name}
	if id != "" {
		req.ServiceID = id
	}
	res := sw.clt.NewRequest().SetData(req).Post(utils.StringBuilder(sw.conf.ServiceManagerDnsUrl, ":", sw.conf.ServiceManagerPort, "/v1/watcher"))
	if err := res.Err(); err != nil {
		return nil, err
	}
	if err := res.GetJson(&list); err != nil {
		return nil, err
	}
	if len(list) == 0 {
		return nil, errors.New("service list is empty")
	}
	// 将获取的服务列表缓存
	ok = sw.mc.Set(key, list, cache.WithEx(sw.conf.ExpireTime))
	if !ok {
		return nil, errors.New("mem-cache Set error")
	}

	return list, nil
}

// FoundServiceWithTags 根据服务名和标签查询服务列表
func (sw *ServWatcher) FoundServiceWithTags(name, tag string) (ServiceQueue, error) {
	key := utils.StringBuilder("service_", name, ":-:", tag)
	list := make([]ServiceInfoAndStatus, 0)
	// 从缓存获取服务列表
	val, ok := sw.mc.Get(key)
	if ok {
		list, ok = val.([]ServiceInfoAndStatus)
		if !ok {
			return nil, errors.New("mem-cache Value Type error")
		}
		return list, nil
	}
	// 缓存不存在或已过期向服务注册发现服务器获取服务列表
	res := sw.clt.NewRequest().SetData(&TagsRequest{ServiceName: name, ServiceTag: tag}).Post(utils.StringBuilder(sw.conf.ServiceManagerDnsUrl, ":", sw.conf.ServiceManagerPort, "/v1/tags"))
	if err := res.Err(); err != nil {
		return nil, err
	}
	if err := res.GetJson(&list); err != nil {
		return nil, err
	}
	if len(list) == 0 {
		return nil, errors.New("service list is empty")
	}
	// 将获取的服务列表缓存
	ok = sw.mc.Set(key, list, cache.WithEx(sw.conf.ExpireTime))
	if !ok {
		return nil, errors.New("mem-cache Set error")
	}

	return list, nil
}

func (sw *ServWatcher) GetServiceTags(name, id string) ([]string, error) {
	key := utils.StringBuilder("service_", name, "_", id, "_tags")
	var list []string
	val, ok := sw.mc.Get(key)
	if ok {
		list, ok = val.([]string)
		if !ok {
			return nil, errors.New("mem-cache Value Type error")
		}
		return list, nil
	}
	res := sw.clt.NewRequest().SetData(&WatcherRequest{ServiceName: name, ServiceID: id}).Post(utils.StringBuilder(sw.conf.ServiceManagerDnsUrl, ":", sw.conf.ServiceManagerPort, "/v1/gettags"))
	if err := res.Err(); err != nil {
		return nil, err
	}
	if err := res.GetJson(&list); err != nil {
		return nil, err
	}
	if len(list) == 0 {
		return nil, errors.New("tags list is empty")
	}
	ok = sw.mc.Set(key, list, cache.WithEx(sw.conf.ExpireTime))
	if !ok {
		return nil, errors.New("mem-cache Set error")
	}
	return list, nil
}

func (sw *ServWatcher) GetServiceIpCfg(serv *ServiceInfoAndStatus, aType int, aUsage string) (ipCfg ServiceInfoSvcAddressInfoCfg) {
	for _, cfg := range serv.ServiceInfoSvcAddressInfoCfgList {
		if cfg.ServiceAddressType == aType && cfg.ServiceAddressUsage == aUsage {
			ipCfg = cfg
		}
	}
	return
}

func (sw *ServWatcher) GetServTags(serv *ServiceInfoAndStatus, key string) string {
	for _, tag := range serv.ServiceTags {
		kv := strings.SplitN(tag, ":", 2)
		if kv[0] == key {
			return kv[1]
		}
	}
	tags, err := sw.GetServiceTags(serv.ServiceName, serv.ServiceID)
	if err != nil {
		return ""
	}
	for _, tag := range tags {
		kv := strings.SplitN(tag, ":", 2)
		if kv[0] == key {
			return kv[1]
		}
	}
	return ""
}

// Random 随机获取一个服务
func (q ServiceQueue) Random() ServiceInfoAndStatus {
	if len(q) == 1 {
		return q[0]
	}
	rand.Seed(time.Now().UnixNano())
	rand.Shuffle(len(q), func(i, j int) {
		q[i], q[j] = q[j], q[i]
	})
	return q[0]
}

// GetByID 根据服务ID获取服务, 如未找到对应ID的服务时, 第二参数为false并随机返回
func (q ServiceQueue) GetByID(id string) (ServiceInfoAndStatus, bool) {
	for _, serv := range q {
		if serv.ServiceID == id {
			return serv, true
		}
	}
	return q.Random(), false
}

func (q ServiceQueue) ListID() []string {
	list := make([]string, 0)
	for _, serv := range q {
		list = append(list, serv.ServiceID)
	}
	return list
}
